package com.shengqin.controller.exam;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.core.util.ZipUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.shengqin.annotations.OperaLog;
import com.shengqin.common.pojo.CommonPage;
import com.shengqin.common.pojo.CommonResult;
import com.shengqin.config.CommonException;
import com.shengqin.entity.*;
import com.shengqin.enums.BusinessType;
import com.shengqin.listeners.ArrangeImportListener;
import com.shengqin.listeners.PlanApplyListener;
import com.shengqin.listeners.model.ArrangeImportModel;
import com.shengqin.pojo.exam.*;
import com.shengqin.pojo.plan.PlanStudentParam;
import com.shengqin.service.IPlanApplyProjectService;
import com.shengqin.service.IPlanService;
import com.shengqin.service.IPlanStudentService;
import com.shengqin.service.ISchoolService;
import com.shengqin.service.impl.PlanStudentServiceImpl;
import com.shengqin.utils.*;
import freemarker.template.TemplateException;
import io.swagger.v3.oas.annotations.Operation;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.util.test.Test;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

@RestController
@RequestMapping("admin/v1/plan/apply")
@Slf4j
public class PlanApplyProjectController {

    @Resource
    private IPlanApplyProjectService planApplyProjectService;
    @Resource
    private ISchoolService schoolService;
    @Resource
    private IPlanService planService;
    @Autowired
    private IPlanStudentService planStudentService;


    @OperaLog(title = "删除报名报项", businessType = BusinessType.DELETE)
    @PostMapping("deleteApplyProject")
    public CommonResult<Boolean> deleteApplyProject(@RequestBody PlanStudentParam planStudentParam){
        log.info("删除报名报项入参 {}", JSON.toJSONString(planStudentParam));
        return CommonResult.data(planApplyProjectService.removeById(planStudentParam.getId()));
    }


    @OperaLog(title = "集体公示导出", businessType = BusinessType.OTHER)
    @Operation(description = "集体公示导出")
    @PostMapping("exportPublic")
    public void exportPublic(@RequestBody CommonPage<PublicityQueryParam> queryPageParam,HttpServletResponse response) throws IOException {
        log.info("集体公示导出入参 {}", JSON.toJSONString(queryPageParam));

        response.reset();
        response.setContentType("application/pdf");
        String filename = System.currentTimeMillis() + ".pdf";
        response.addHeader("Content-Disposition", "inline; filename=" + URLUtil.encode(filename, CharsetUtil.CHARSET_UTF_8));

        List<PlanApplyProject> applyProjectList = planApplyProjectService.queryTestProjectResultList(queryPageParam);
        Map<String,Object> resultMap = new HashMap<>();
        List<String> htmlStrList = new ArrayList<>();
        PlanApplyProject firstApplyProject = applyProjectList.get(0);
        Integer planId = firstApplyProject.getPlanId();
        Plan plan = planService.getById(planId);
        Map<String,Object> data = new HashMap<>();
        data.put("planName",plan.getName());
        data.put("schoolName",firstApplyProject.getSchoolName());
        data.put("groupNo",firstApplyProject.getGroupNo());
        data.put("gradeName",firstApplyProject.getGradeName());

        List<JSONObject> testItems = new ArrayList<>();
        List<PlanApplyProject> sortList = applyProjectList.stream().sorted(
                Comparator.comparing(PlanApplyProject::getGradeName)
                        .thenComparing(PlanApplyProject::getClassesName)
                        .thenComparing(PlanApplyProject::getGroupInNo)).collect(Collectors.toList());
        sortList.forEach(item -> {
            item.setGroupNoQrCode(Base64.encode(QRCodeUtil.createQrCode(item.getGroupNo())));
            JSONObject testItem = new JSONObject();
            testItem.put("classesName",item.getClassesName());
            testItem.put("name",item.getName());
            testItem.put("sex",item.getSex());
            testItem.put("studyCode",item.getStudyCode());
            List<TestResult> testResultList = new ArrayList<>();

//            List<TestResult> weightHeightList = item.getTestResultList().stream().filter(testResult -> testResult.getPrjName().equals("身高体重")).collect(Collectors.toList());
            Map<String,List<TestResult>> prjResultMap = new HashMap<>();
            if(!CollectionUtils.isEmpty(item.getTestResultList())){
                prjResultMap = item.getTestResultList().stream().collect(Collectors.groupingBy(TestResult::getPrjName));
            }
            Map<String, List<TestResult>> finalPrjResultMap = prjResultMap;
            firstApplyProject.getProjectNameList().forEach(projectName -> {
                String prjName = projectName;
                if(prjName.equalsIgnoreCase("身高体重")){
                    List<TestResult> whResults = finalPrjResultMap.getOrDefault(prjName, null);
                    if(CollectionUtils.isEmpty(whResults)){
                        prjName = "BMI";
                    }
                }
                if(finalPrjResultMap.containsKey(prjName)){
                    TestResult result = finalPrjResultMap.get(prjName).get(0);
                    testResultList.add(result);
                } else {
                    TestResult testResult = new TestResult();
                    testResult.setResultType("未测试");
                    testResult.setPrjName(prjName);
                    testResult.setScore(BigDecimal.ZERO);
                    testResult.setFinalScore(BigDecimal.ZERO);
                    testResultList.add(testResult);
                }
            });
            testItem.put("testResultList",testResultList);
            testItem.put("summaryResult",item.getSummaryResult());
            testItem.put("groupInNo",item.getGroupInNo());
            testItems.add(testItem);
        });

        for (int i = 0; i < firstApplyProject.getProjectNameList().size(); i++) {
            if(firstApplyProject.getProjectNameList().get(i).equalsIgnoreCase("身高体重")){
                firstApplyProject.getProjectNameList().set(i,"BMI");
            }
        }
        data.put("projectNameList",firstApplyProject.getProjectNameList());

        data.put("testItems",testItems);
        data.put("showScore",queryPageParam.getQueryParam().getShowScore());
        data.put("showLevel",queryPageParam.getQueryParam().getShowLevel());
        resultMap.put("data",data);
        try {
            String templateName = "batch_public.ftl";
            if(!CollectionUtils.isEmpty(queryPageParam.getQueryParam().getStudyCodeList())){
                templateName = "batch_public_dy.ftl";
            }
            if(queryPageParam.getQueryParam().getTemplateStyle().equals("custom")){
                templateName = "batch_public_custom.ftl";
                if(!CollectionUtils.isEmpty(queryPageParam.getQueryParam().getStudyCodeList())){
                    templateName = "batch_public_custom_dy.ftl";
                }
            }
            String html = FreemarkerUtil.loadTemplateFromFile(templateName,resultMap);
            htmlStrList.add(html);
        } catch (IOException | TemplateException e) {
            throw new RuntimeException(e);
        }

        byte[] pdfOut = PdfUtil.html2Pdf(htmlStrList);

        OutputStream out = response.getOutputStream();
        out.write(pdfOut);
        out.close();
    }

    @OperaLog(title = "锁定成绩", businessType = BusinessType.OTHER)
    @Operation(description = "锁定成绩")
    @PostMapping("lockResult")
    public CommonResult<Boolean> lockResult(@RequestBody PublicityQueryParam publicityQueryParam){
        log.info("锁定成绩入参 {}", JSON.toJSONString(publicityQueryParam));
        return CommonResult.data(planApplyProjectService.lockResult(publicityQueryParam));
    }

    @OperaLog(title = "查询单人公示", businessType = BusinessType.OTHER)
    @Operation(description = "查询单人公示")
    @PostMapping("queryTestProjectResult")
    public CommonResult<Page<PlanApplyProject>> queryTestProjectResult(@RequestBody CommonPage<PublicityQueryParam> queryPageParam){
        log.info("查询单人公示入参 {}", JSON.toJSONString(queryPageParam));
        return CommonResult.data(planApplyProjectService.queryTestProjectResult(queryPageParam));
    }

    @OperaLog(title = "集体取消检录", businessType = BusinessType.OTHER)
    @Operation(description = "集体取消检录")
    @PostMapping("cancelCheckGroup")
    public CommonResult<List<PlanApplyProject>> cancelCheckGroup(@RequestBody CheckInParam checkInParam){
        log.info("集体取消检录入参 {}", JSON.toJSONString(checkInParam));
        return CommonResult.data(planApplyProjectService.cancelCheckGroup(checkInParam));
    }

    @OperaLog(title = "集体检录", businessType = BusinessType.OTHER)
    @Operation(description = "集体检录")
    @PostMapping("checkInGroup")
    public CommonResult<List<PlanApplyProject>> checkInGroup(@RequestBody CheckInParam checkInParam){
        log.info("集体检录入参 {}", JSON.toJSONString(checkInParam));
        return CommonResult.data(planApplyProjectService.checkInGroup(checkInParam));
    }

    @Operation(description = "查询集体检录列表")
    @PostMapping("queryCheckInGroup")
    public CommonResult<List<PlanApplyProject>> queryCheckInGroup(@RequestBody CheckInParam checkInParam){
        return CommonResult.data(planApplyProjectService.queryCheckInGroup(checkInParam));
    }

    @Operation(description = "查询检录统计数量")
    @PostMapping("queryCheckInCount")
    public CommonResult<Map<String,Long>> queryCheckInCount(@RequestBody CheckInParam checkInParam){
        return CommonResult.data(planApplyProjectService.queryCheckInCount(checkInParam));
    }

    @OperaLog(title = "取消检录", businessType = BusinessType.OTHER)
    @Operation(description = "取消检录")
    @PostMapping("cancelCheck")
    public CommonResult<Boolean> cancelCheck(@RequestBody CheckInParam checkInParam){
        log.info("取消检录入参 {}", JSON.toJSONString(checkInParam));
        return CommonResult.data(planApplyProjectService.cancelCheck(checkInParam));
    }


    @OperaLog(title = "单人检录", businessType = BusinessType.OTHER)
    @Operation(description = "单个检录")
    @PostMapping("checkIn")
    public CommonResult<List<PlanApplyProject>> checkIn(@RequestBody CheckInParam checkInParam){
        log.info("单个检录入参 {}", JSON.toJSONString(checkInParam));
        return CommonResult.data(planApplyProjectService.checkIn(checkInParam));
    }

    @Operation(description = "更改报名报项")
    @PostMapping("updateProjectNames")
    public CommonResult<Boolean> updateProjectNames(@RequestBody PlanApplyProject planApplyProject){
        log.info("更改报名报项入参 {}", JSON.toJSONString(planApplyProject));
        return CommonResult.data(planApplyProjectService.updateProjectNames(planApplyProject));
    }

    @PostMapping("queryPage")
    public CommonResult<Page<PlanApplyProject>> queryPage(@RequestBody CommonPage<PlanApplyQueryParam> pageQueryParam){
        return CommonResult.data(planApplyProjectService.queryPage(pageQueryParam));
    }

    @PostMapping("clearApply")
    public CommonResult<Boolean> clearApply(@RequestBody PlanApplyQueryParam queryParam){
        return CommonResult.data(planApplyProjectService.clearApply(queryParam));
    }

    @Operation(description = "导入报名报项")
    @PostMapping("import")
    public CommonResult importApplyFile(@RequestPart("file") MultipartFile file, @RequestParam Integer planId) {
        log.info("导入报名报项{}",planId);
        try {
            EasyExcel.read(file.getInputStream(), PlanApplyProject.class, new PlanApplyListener(planApplyProjectService, planId)).sheet().doRead();
        } catch (ExcelDataConvertException e) {
            String message = String.format("第%s行，第%s列解析异常，请修改后重新上传", e.getRowIndex() + 1, e.getColumnIndex() + 1);
            throw new CommonException(message);
        } catch (Exception e) {
            throw new CommonException("上传失败，请重试。错误信息：" + e.getMessage());
        }
        return CommonResult.ok().setMsg("导入成功");
    }

    @OperaLog(title = "报名报项编排", businessType = BusinessType.OTHER)
    @PostMapping("arrange")
    @Operation(description = "编排")
    public CommonResult<Boolean> arrange(@RequestBody ArrangeParam arrangeParam){
        log.info("编排入参{}",JSON.toJSONString(arrangeParam));
        return CommonResult.data(planApplyProjectService.arrange(arrangeParam));
    }

    @PostMapping("importCustomArrange")
    @Operation(description = "导入自定义编排")
    public CommonResult importCustomArrange(@RequestPart("file") MultipartFile file,@RequestParam Integer planId){
        try {
            EasyExcel.read(file.getInputStream(), ArrangeImportModel.class, new ArrangeImportListener(planApplyProjectService, planId)).sheet().doRead();
        } catch (ExcelDataConvertException e) {
            String message = String.format("第%s行，第%s列解析异常，请修改后重新上传", e.getRowIndex() + 1, e.getColumnIndex() + 1);
            throw new CommonException(message);
        } catch (Exception e) {
            throw new CommonException("上传失败，请重试。错误信息：" + e.getMessage());
        }
        return CommonResult.ok().setMsg("导入成功");
    }

    @PostMapping("queryArrangePage")
    @Operation(description = "查询编排结果")
    public CommonResult<Page<PlanApplyProject>> queryArrangePage(@RequestBody CommonPage<PlanApplyQueryParam> pageQueryParam){
        return CommonResult.data(planApplyProjectService.queryArrangePage(pageQueryParam));
    }

    @PostMapping("exportArrangeTicket")
    public void exportArrangeTicket(@RequestBody PlanApplyQueryParam queryParam, HttpServletResponse response) throws Exception {
        List<PlanStudent> planStudents = planStudentService.listByPlanId(queryParam.getPlanId());
        Map<String,PlanStudent> planStudentMap = planStudents.stream().collect(Collectors.toMap(PlanStudent::getStudyCode, s -> s));
        List<PlanApplyProject> applyProjectList = planApplyProjectService.listByPlanId(queryParam.getPlanId());
        applyProjectList.forEach(item -> {
            item.setSex(item.getSex().equalsIgnoreCase("1") ? "男" : "女");
            item.setGradeName(GradeUtil.getGradeName(item.getGradeId()));
            item.setStudyCodeQrCode(Base64.encode(QRCodeUtil.createQrCode(item.getStudyCode(),70)));
            item.setProjectNameList(Arrays.asList(item.getProjectNames().split(",")));
            if(StringUtils.hasLength(item.getGroupNo())){
                String[] groupNoList = item.getGroupNo().split("-");
                if(groupNoList.length == 1){
                    item.setShowGroupNo(groupNoList[0]);
                } else if (groupNoList.length == 3){
                    item.setShowGroupNo(groupNoList[2]);
                }
            }
            PlanStudent student = planStudentMap.getOrDefault(item.getStudyCode(),null);
            if (student != null) {
                item.setAvatarUrl(student.getAvatarUrl());
            }
        });

        Map<Integer,List<PlanApplyProject>> schoolApplyMap = applyProjectList.stream().collect(Collectors.groupingBy(PlanApplyProject::getSchoolId));
        List<File> files = new ArrayList<>();
        if (queryParam.getExportArrangeTicketType() == 1) {
            schoolApplyMap.forEach((schoolId, planApplyProjects) -> {
                Map<String, List<PlanApplyProject>> groupMap = planApplyProjects.stream().collect(Collectors.groupingBy(PlanApplyProject::getGroupNo));
                Map<String, Object> dataModel = new HashMap<>();
                List<String> htmlStrList = new ArrayList<>();
                School school = schoolService.getById(schoolId);
                groupMap.keySet().stream().sorted(new StringAndNumberSorter.AlphaNumericComparator()).forEach(groupNo -> {
                    List<PlanApplyProject> groupProjects = groupMap.get(groupNo);
                    dataModel.put("tickets", groupProjects.stream().sorted(Comparator.comparingInt(PlanApplyProject::getGroupInNo)).collect(Collectors.toList()));
                    try {
                        String html = FreemarkerUtil.loadTemplateFromFile("admission-ticket.ftl",dataModel);
                        htmlStrList.add(html);
                    } catch (IOException | TemplateException e) {
                        throw new RuntimeException(e);
                    }
                });
                byte[] pdfOut = PdfUtil.html2Pdf(htmlStrList);
                File pdfFile = byte2Pdf(pdfOut,school.getName() + "编排分组准考证.pdf");
                files.add(pdfFile);
            });
        } else {
            schoolApplyMap.forEach((schoolId, planApplyProjects) -> {
                Map<String, List<PlanApplyProject>> classesMap = planApplyProjects.stream().sorted(Comparator.comparing(PlanApplyProject::getGroupNo)).collect(Collectors.groupingBy(PlanApplyProject::getClassesName));
                List<String> classesNames = classesMap.keySet().stream().sorted(new StringAndNumberSorter.AlphaNumericComparator()).collect(Collectors.toList());
                Map<String, Object> dataModel = new HashMap<>();
                List<String> htmlStrList = new ArrayList<>();
                School school = schoolService.getById(schoolId);
                classesNames.forEach(classesName -> {
                    List<PlanApplyProject> classesProjects = classesMap.get(classesName);
                    dataModel.put("tickets", classesProjects.stream().sorted(Comparator.comparingInt(PlanApplyProject::getGroupInNo)).collect(Collectors.toList()));
                    try {
                        String html = FreemarkerUtil.loadTemplateFromFile("admission-ticket.ftl",dataModel);
                        htmlStrList.add(html);
                    } catch (IOException | TemplateException e) {
                        throw new RuntimeException(e);
                    }
                });
                byte[] pdfOut = PdfUtil.html2Pdf(htmlStrList);
                File pdfFile = byte2Pdf(pdfOut,school.getName() + "编排分组准考证.pdf");
                files.add(pdfFile);
            });
        }

        Plan plan = planService.getById(queryParam.getPlanId());
        File targetZipFile = new File(plan.getName() + "编排分组准考证.zip");
        generateZip(targetZipFile,files,false);
        OutputStream out = response.getOutputStream();
        FileChannel fileChannel = new RandomAccessFile(targetZipFile, "r").getChannel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        WritableByteChannel writableByteChannel = Channels.newChannel(out);
        while (fileChannel.read(byteBuffer) != -1) {
            byteBuffer.flip();
            while (byteBuffer.hasRemaining()) {
                writableByteChannel.write(byteBuffer);
            }
            byteBuffer.clear();
        }
        fileChannel.close();
        writableByteChannel.close();
        out.close();
        if (targetZipFile.exists()) {
            targetZipFile.delete();
        }
        if(!CollectionUtils.isEmpty(files)) {
            files.forEach(File::delete);
        }

    }

    @PostMapping("exportArrangeExcel")
    public void exportArrangeExcel(@RequestBody PlanApplyQueryParam queryParam, HttpServletResponse response) throws Exception {
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
        String fileName = URLEncoder.encode("评分等级汇总", String.valueOf(StandardCharsets.UTF_8)).replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");

        List<PlanApplyProject> applyProjectList = planApplyProjectService.listByPlanId(queryParam.getPlanId());
        applyProjectList = applyProjectList.stream().sorted(Comparator.comparing(PlanApplyProject::getGroupNo).thenComparing(PlanApplyProject::getGradeId).thenComparing(PlanApplyProject::getClassesName)).collect(Collectors.toList());

        List<ExportArrangeModel> exportArrangeModelList = new ArrayList<>();
        applyProjectList.forEach(item -> {
            ExportArrangeModel exportArrangeModel = new ExportArrangeModel();
            BeanUtils.copyProperties(item,exportArrangeModel);
            exportArrangeModelList.add(exportArrangeModel);
        });

        EasyExcel.write(response.getOutputStream(), ExportArrangeModel.class)
                .registerWriteHandler(new CustomCellWriteWidthConfig()).sheet("sheet1").doWrite(exportArrangeModelList);
    }

    @PostMapping("/exportArrange")
    public void exportArrange(@RequestBody CommonPage<PlanApplyQueryParam> pageQueryParam, HttpServletResponse response) throws Exception {
        response.reset();
        response.setContentType("application/octet-stream;charset=UTF-8");
        String filename = System.currentTimeMillis() + ".zip";
        response.addHeader("Content-Disposition", "inline; filename=" + URLUtil.encode(filename, CharsetUtil.CHARSET_UTF_8));

        List<PlanApplyProject> applyProjectList = planApplyProjectService.exportArrangePage(pageQueryParam);
        Map<String,Object> resultMap = new HashMap<>();
        Map<Integer,List<PlanApplyProject>> schoolApplyMap = applyProjectList.stream().collect(Collectors.groupingBy(PlanApplyProject::getSchoolId));
        List<File> files = new ArrayList<>();
        Integer planId = applyProjectList.get(0).getPlanId();
        schoolApplyMap.forEach((schoolId,records) -> {
            List<String> htmlStrList = new ArrayList<>();
            School school = schoolService.getById(schoolId);
            List<PlanApplyProject> sortByList = records.stream().sorted(new StringAndNumberSorter.SchoolGroupNoComparator()).collect(Collectors.toList());
            sortByList.forEach(item -> {
                item.setGroupNoQrCode(Base64.encode(QRCodeUtil.createQrCode(item.getGroupNo())));
                resultMap.put("data",item);
                try {
                    String templateName = "arrange.ftl";
                    if(pageQueryParam.getQueryParam().getTemplateStyle().equals("custom")){
                        templateName = "arrange_custom.ftl";
                    }
                    String html = FreemarkerUtil.loadTemplateFromFile(templateName,resultMap);
                    htmlStrList.add(html);
                } catch (IOException | TemplateException e) {
                    throw new RuntimeException(e);
                }
            });

            byte[] pdfOut = PdfUtil.html2Pdf(htmlStrList);

            File pdfFile = byte2Pdf(pdfOut,school.getName() + "编排分组表.pdf");
            files.add(pdfFile);
        });
        Plan plan = planService.getById(planId);
        File targetZipFile = new File(plan.getName() + "编排分组表.zip");
        generateZip(targetZipFile,files,false);
        OutputStream out = response.getOutputStream();
        FileChannel fileChannel = new RandomAccessFile(targetZipFile, "r").getChannel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        WritableByteChannel writableByteChannel = Channels.newChannel(out);
        while (fileChannel.read(byteBuffer) != -1) {
            byteBuffer.flip();
            while (byteBuffer.hasRemaining()) {
                writableByteChannel.write(byteBuffer);
            }
            byteBuffer.clear();
        }
        fileChannel.close();
        writableByteChannel.close();
        out.close();
        if (targetZipFile.exists()) {
            targetZipFile.delete();
        }
        if(!CollectionUtils.isEmpty(files)) {
            files.forEach(File::delete);
        }
    }

    public static void generateZip(File targetZipFile, List<File> sourceFiles, boolean dirWithFlag) {
        if (CollUtil.isNotEmpty(sourceFiles)) {
            File[] fileArr = sourceFiles.toArray(new File[]{});
            ZipUtil.zip(targetZipFile, dirWithFlag, fileArr);
        }
    }

    public File byte2Pdf(byte[] pdfOut,String name) {
        File file = new File(name);
        try (FileOutputStream fos = new FileOutputStream(file)) {
            fos.write(pdfOut);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return file;
    }
}


